package morm

import (
	"api/libs/errs"
	"api/libs/orm"
	"api/public/global"
	"context"
	"errors"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"reflect"
)

type Factory struct {
	Doc Doc
	Ctx context.Context
	Db  *mongo.Database
	//事件开关
	IsEventOn bool
}

func NewFactory(argus ...interface{}) *Factory {
	f := new(Factory)
	for _, arg := range argus {
		tp := reflect.TypeOf(arg)
		switch tp.String() {
		case "context.Context":
			f.Ctx = arg.(context.Context)
		case "*mongo.Database":
			f.Db = arg.(*mongo.Database)
		default:
		}
	}
	return f
}

type Doc interface {
	DocName() string
	GetId() primitive.ObjectID
	SetId(id primitive.ObjectID)
}

func (this *Factory) SetDoc(doc Doc) {
	this.Doc = doc
}

func (this *Factory) SetCtx(ctx context.Context) {
	this.Ctx = ctx
}
func (this *Factory) GetCtx() context.Context {
	return this.Ctx
}
func (this *Factory) DB() *mongo.Database {
	if this.Db == nil {
		this.Db = global.MGO
	}
	return this.Db
}
func (this *Factory) SetDb(db *mongo.Database) {
	this.Db = db
}

func (this *Factory) Create(doc Doc) error {
	this.TriggerBeforeCreate(doc)
	rst, e := this.DB().Collection(doc.DocName()).InsertOne(this.Ctx, doc)
	if errs.Try(e) {
		return e
	}
	if oid, ok := rst.InsertedID.(primitive.ObjectID); ok {
		doc.SetId(oid)
	}
	this.TriggerAfterCreate(doc)
	return nil
}
func (this *Factory) Update(doc Doc) error {
	this.TriggerBeforeUpdate(doc)
	rst, e := this.DB().Collection(doc.DocName()).UpdateByID(this.Ctx, doc.GetId(), bson.M{"$set": doc})
	if errs.Try(e) {
		return e
	}
	if rst.ModifiedCount != 1 {
		return errors.New("未更新")
	}
	this.TriggerAfterUpdate(doc)
	return nil
}
func (this *Factory) Delete(doc Doc) error {
	this.TriggerDelete(doc)
	rst, e := this.DB().Collection(doc.DocName()).DeleteOne(this.Ctx, doc.GetId())
	if errs.Try(e) {
		return e
	}
	if rst.DeletedCount != 1 {
		return errors.New("未删除任何内容")
	}
	return nil
}

func (this *Factory) FindBy(docs interface{}, builder *orm.HttpQuery) error {
	if builder.Page < 1 {
		builder.Page = 1
	}
	if builder.Limit < 0 {
		builder.Limit = 10
	}
	cursor, e := this.DB().Collection(this.Doc.DocName()).Find(
		this.Ctx,
		CreateFilter(builder.Conditions),
		CreateOption(builder.Order, builder.Limit*builder.Page-builder.Limit, builder.Limit),
	)
	if errs.Try(e) {
		return e
	}
	e = cursor.All(this.Ctx, docs)
	if errs.Try(e) {
		return e
	}
	return nil
}
func (this *Factory) Count(builder *orm.HttpQuery) (int64, error) {
	count, e := this.DB().Collection(this.Doc.DocName()).CountDocuments(
		this.Ctx,
		CreateFilter(builder.Conditions),
	)
	if errs.Try(e) {
		return -1, e
	}
	return count, nil
}

func (this *Factory) FindOneBy(doc Doc, builder *orm.HttpQuery) error {
	cursor := this.DB().Collection(doc.DocName()).FindOne(
		this.Ctx,
		CreateFilter(builder.Conditions),
		CreateFindOneOption(builder.Order),
	)
	e := cursor.Decode(doc)
	if errs.Try(e) {
		return e
	}
	return nil
}

func (this *Factory) Find(doc Doc, id string) error {
	qb := NewQueryBuilder()
	pid, e := primitive.ObjectIDFromHex(id)
	if errs.Try(e) {
		return e
	}
	qb.Conditions["_id"] = pid
	return this.FindOneBy(doc, qb)
}
